﻿using DPSBase;
using NetworkCommsDotNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Phone.Net.NetworkInformation;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Shell;
using System.Windows.Threading;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using GalaSoft.MvvmLight.Messaging;
using Jive.Helpers;
using JmShared;

namespace Jive.Network
{
    public class NetworkConnection
    {
        private int _messageID;
        private string _deviceID;
        private Dictionary<int, Dictionary<int, ChatMessage>> _queuedMessages;
        private Dictionary<int, int> _processedMessageCount;
        private Dictionary<int, DateTime> _processedMessageTime;
        private Dictionary<int, List<ChatMessage>> _addedBatchMessages;
        private object ReceiveChatMessageLockObject = new object();
        private string _currentUserID;

        public event EventHandler<EventArgs> OnDisconnected;
        public event EventHandler<MessageQueueUpdateEventArgs> MessageQueueItemReceived;
        public event EventHandler<ChatMessageEventArgs> ChatMessageReceived;
        public event EventHandler<ChatMessageEventArgs> ChatMessageSendt;
        public event EventHandler<ChatReceiptEventArgs> ChatReceiptReceived;
        public event EventHandler<HostnameLookupEventArgs> HostnameLookupSucceeded;

        public ConnectionInfo MasterConnectionInfo { get; set; }
        public int RemainingMessages { get; set; }
        public NetworkConnection(string uniqueDeviceID)
        {
            this._deviceID = uniqueDeviceID;
        }

        /// <summary>
        /// Sends the chatmessage to the server
        /// </summary>
        /// <param name="msg"></param>
        public bool SendMessage(ChatMessage msg)
        {
            if (MasterConnectionInfo == null)
                return false;
            if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                return false;
            try
            {
                if (msg.MessageType == MessageType.Message || msg.MessageType == MessageType.PM)
                    msg.MessageID = Interlocked.Increment(ref _messageID);
                TCPConnection.GetConnection(MasterConnectionInfo).SendObject("ChatMessage", msg);
                if(ChatMessageSendt!=null)
                    ChatMessageSendt(this,new ChatMessageEventArgs(msg));
            }
            catch (CommsException err)
            {
                Debug.WriteLine("Error sending message: {0}", err);
                return false;
            }
            return true;
        }
        public void SendStatusMessage(MessageType mt, string message)
        {
            ChatMessage msg = new ChatMessage { SenderID = _currentUserID, MessageType = mt, Message = message };
            SendMessage(msg);
        }

        public void SendStatusMessage(MessageType mt, string message, string[] targets)
        {
            ChatMessage msg = new ChatMessage { SenderID = _currentUserID, MessageType = mt, Message = message, Recipients = targets };
            SendMessage(msg);
        }

        
        public void Login(string server, string username, string password, int port)
        {
            Thread t = new Thread(() =>
            {
                bool isConnected = false;

                try
                {
                    isConnected = Connect(server, username, password, port);
                }
                catch (Exception err)
                {

                    Messenger.Default.Send(err.Message);

                }

            }) { Name = "LoginThread", IsBackground = true };
            t.Start();
        }

        /// <summary>
        /// Connects to the server
        /// </summary>
        /// <returns></returns>
        private bool Connect(string server, string username, string password, int port)
        {
            try
            {
                if (MasterConnectionInfo != null)
                {
                    if (MasterConnectionInfo.ConnectionState == ConnectionState.Established)
                        TCPConnection.GetConnection(MasterConnectionInfo).CloseConnection(false);

                    MasterConnectionInfo = null;
                }

                if (String.IsNullOrEmpty(server))
                {
                    Messenger.Default.Send("No servers configured");
                    if (OnDisconnected != null)
                        OnDisconnected(this, null);
                    return false;
                }
                IPEndPoint[] serverAddresses = { };
                bool done = false;

                DeviceNetworkInformation.ResolveHostNameAsync(new DnsEndPoint(server, port), networkInfo =>
               {
                   try
                   {
                       var add = networkInfo.IPEndPoints;
                       serverAddresses = add;
                   }
                   catch (Exception err)
                   {
                       Messenger.Default.Send("Exception in hostname lookup : " + err.Message);
                       if (OnDisconnected != null)
                           OnDisconnected(this, null);
                   }
                   finally
                   {
                       done = true;
                   }

               }, null);
                while (!done)
                    Thread.Sleep(5);

                string connectionIP = string.Empty;
#if DEBUG
                connectionIP = "192.168.1.59";
#else

                if (serverAddresses == null || serverAddresses.Length == 0)
                {
                    if (Settings.HostnameCache != null)
                    {
                        if (Settings.HostnameCache.ContainsKey(Settings.ServerSetting))
                        {
                            connectionIP = Settings.HostnameCache[Settings.ServerSetting];
                        }
                        else if (Settings.HostnameCache.ContainsKey(Settings.ServerSetting.ToLower()))
                        {
                            connectionIP = Settings.HostnameCache[Settings.ServerSetting.ToLower()];
                        }
                        else
                            throw new NetworkException(NetworkError.NameResolutionNoHostAddresses);
                    }
                    else
                        throw new NetworkException(NetworkError.NameResolutionNoHostAddresses);
                }
                else
                    connectionIP = serverAddresses[0].Address.ToString();

#endif
                MasterConnectionInfo = new ConnectionInfo(connectionIP, port);
                var dataProcessors = new List<DataProcessor>();
                dataProcessors.Add(DPSManager.GetDataProcessor<RijndaelPSKEncrypter>());

                var dataProcessorOptions = new Dictionary<string, string>();
                RijndaelPSKEncrypter.AddPasswordToOptions(dataProcessorOptions, "fg342ssdfs43232fdfgdg3452fdsgbvcvcblkiuyoaxc");
                DataSerializer dataSerializer = DPSManager.GetDataSerializer<ProtobufSerializer>();
                NetworkComms.DefaultSendReceiveOptions = new SendReceiveOptions(dataSerializer, dataProcessors, dataProcessorOptions);
                NetworkComms.ConnectionEstablishTimeoutMS = 2000;
                NetworkComms.ConnectionAliveTestTimeoutMS = 2000;
                NetworkComms.AppendGlobalIncomingPacketHandler<ChatMessage>("ChatMessage", ProcessMessage);
                NetworkComms.AppendGlobalIncomingPacketHandler<ChatReceipt>("Receipt", ProcessReceipt);
                NetworkComms.AppendGlobalConnectionCloseHandler(client_OnDisconnected);
                NetworkComms.IgnoreUnknownPacketTypes = true;
                NetworkComms.EnablePacketCheckSumValidation = true;

                TCPConnection.GetConnection(MasterConnectionInfo).EstablishConnection(); //Connect

                if (MasterConnectionInfo.ConnectionState != ConnectionState.Established)
                {
                    Messenger.Default.Send("Connection failed");
                    return false;
                }

                if (HostnameLookupSucceeded != null && serverAddresses!=null && serverAddresses.Length>0)
                    HostnameLookupSucceeded(this, new HostnameLookupEventArgs(serverAddresses[0].Address.ToString()));
                UserInfo user = new UserInfo
                {
                    UserName = username,
                    ClientStatus = ClientStatus.Available,
                    ID = _deviceID
                };
                _currentUserID = user.UserID;
                ChatMessage msg = new ChatMessage
                {
                    MessageType = MessageType.Login,
                    Message = StaticMethods.ObjectToString<LoginInfo>(new LoginInfo
                    {
                        UserName = username,
                        Password = password,
                        UserInfo = user
                    }),
                    SenderID = user.UserID
                };

                SendMessage(msg);

                _queuedMessages = new Dictionary<int, Dictionary<int, ChatMessage>>();
                _processedMessageTime = new Dictionary<int, DateTime>();
                _addedBatchMessages = new Dictionary<int, List<ChatMessage>>();
                _processedMessageCount = new Dictionary<int, int>();

                return true;
            }
            catch
            {
                throw;

            }
        }

        private void ProcessReceipt(PacketHeader packetheader, Connection connection, ChatReceipt receipt)
        {
            if (ChatReceiptReceived != null)
                ChatReceiptReceived(this, new ChatReceiptEventArgs(receipt));
        }
        /// <summary>
        /// Process received ChatMessage objects
        /// </summary>
        /// <param name="header"></param>
        /// <param name="connection"></param>
        /// <param name="msg"></param>
        private void ProcessMessage(PacketHeader header, Connection connection, ChatMessage msg)
        {
            if (msg.QueuedMessageCount > 1)
            {
                lock (this.ReceiveChatMessageLockObject)
                {
                    if (!this._processedMessageCount.ContainsKey(msg.QueuedBatchNumber))
                        this._processedMessageCount[msg.QueuedBatchNumber] = 0;

                    if (!_queuedMessages.ContainsKey(msg.QueuedBatchNumber))
                        _queuedMessages[msg.QueuedBatchNumber] = new Dictionary<int, ChatMessage>();

                    if (!_addedBatchMessages.ContainsKey(msg.QueuedBatchNumber))
                        _addedBatchMessages[msg.QueuedBatchNumber] = new List<ChatMessage>();

                    int lastProcessedNum = _addedBatchMessages[msg.QueuedBatchNumber].Count > 0 ? _addedBatchMessages[msg.QueuedBatchNumber].Last().QueuedMessageNumber : 0;
                    _processedMessageCount[msg.QueuedBatchNumber]++;
                    _processedMessageTime[msg.QueuedBatchNumber] = DateTime.Now;

                    RemainingMessages = msg.QueuedMessageCount - _processedMessageCount[msg.QueuedBatchNumber];
                    if (MessageQueueItemReceived != null)
                        MessageQueueItemReceived(this, new MessageQueueUpdateEventArgs(_processedMessageCount[msg.QueuedBatchNumber], msg.QueuedMessageCount, msg.QueuedBatchNumber));

                    if (msg.QueuedMessageNumber > lastProcessedNum + 1 && msg.QueuedMessageNumber > 1)
                    {
                        _queuedMessages[msg.QueuedBatchNumber][msg.QueuedMessageNumber] = msg;

                        while (true)
                        {
                            int i = 1;
                            if (_queuedMessages[msg.QueuedBatchNumber].ContainsKey(lastProcessedNum + i))
                            {
                                HandleQueuedMessage(_queuedMessages[msg.QueuedBatchNumber][lastProcessedNum + i]);
                                System.Diagnostics.Debug.WriteLine(string.Format("handling previously queued message number: {0} for batch number {1}", (lastProcessedNum + i), msg.QueuedBatchNumber));
                                i++;
                            }
                            else
                                break;
                        }
                    }
                    else
                    {
                        HandleQueuedMessage(msg);
                        System.Diagnostics.Debug.WriteLine(string.Format("handling message number: {0} for batch number {1}", (lastProcessedNum + 1), msg.QueuedBatchNumber));
                    }

                    if (_processedMessageCount[msg.QueuedBatchNumber] == msg.QueuedMessageCount)
                    {
                        List<ChatMessage> messages = _queuedMessages[msg.QueuedBatchNumber].Values.OrderBy(x => x.QueuedMessageNumber).ToList();
                        foreach (ChatMessage m in messages)
                            HandleQueuedMessage(m);

                        this._queuedMessages.Remove(msg.QueuedBatchNumber);
                        this._processedMessageCount.Remove(msg.QueuedBatchNumber);
                        _processedMessageTime.Remove(msg.QueuedBatchNumber);
                    }
                }
            }
            else
                ProcessChatMessage(msg); //1 item in the queue, so we skip the queue system and just process it.

        }

        private void HandleQueuedMessage(ChatMessage msg)
        {
            ProcessChatMessage(msg);
            _addedBatchMessages[msg.QueuedBatchNumber].Add(msg);
            _queuedMessages[msg.QueuedBatchNumber].Remove(msg.QueuedMessageNumber);
        }
        public void ProcessChatMessage(ChatMessage msg)
        {
            if (ChatMessageReceived != null)
                ChatMessageReceived(this, new ChatMessageEventArgs(msg));
        }

        /// <summary>
        /// Triggers on disconnect from the server.
        /// </summary>
        private void client_OnDisconnected(Connection connection)
        {

            _currentUserID = string.Empty;

            NetworkComms.RemoveGlobalConnectionCloseHandler(client_OnDisconnected);
            NetworkComms.RemoveGlobalIncomingPacketHandler("ChatMessage");

            if (OnDisconnected != null)
                OnDisconnected(this, null);
        }
    }

    public class ChatMessageEventArgs : EventArgs
    {
        public ChatMessage Message { get; set; }
        public ChatMessageEventArgs(ChatMessage msg)
        {
            this.Message = msg; ;
        }
    }
    public class ChatReceiptEventArgs : EventArgs
    {
        public ChatReceipt Receipt { get; set; }
        public ChatReceiptEventArgs(ChatReceipt receipt)
        {
            this.Receipt = receipt; ;
        }
    }

    public class HostnameLookupEventArgs : EventArgs
    {
        public string IPAddress { get; private set; }
        public HostnameLookupEventArgs(string ip)
        {
            this.IPAddress = ip;
        }
    }

    public class MessageQueueUpdateEventArgs : EventArgs
    {
        public int ReceivedMessages { get; set; }
        public int TotalMessagesInQueue { get; set; }
        public int BatchNumber { get; set; }
        public MessageQueueUpdateEventArgs(int receivedMessages, int totalMessages, int batchNumber)
        {
            this.ReceivedMessages = receivedMessages;
            this.TotalMessagesInQueue = totalMessages;
            this.BatchNumber = batchNumber;
        }
    }

}
